// 
// XmppChatWidget.cs
//
// Copyright © 2007 Ben Motmans <ben.motmans@gmail.com>
// Copyright © 2007 Philippe Durand <draekz@gmail.com>
// Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.IO;
using System.Web;
using System.Threading;
using System.Collections.Generic;

using Gtk;
using Glade;
using Pango;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Client.GtkGui;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;

using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Messaging;

namespace Galaxium.Protocol.Xmpp.GtkGui
{
	public class XmppChatWidget: BasicChatWidget
	{
		private Timer _typing_timer;
		
		public new XmppConversation Conversation
		{
			get { return base.Conversation as XmppConversation; }
		}
		
		internal XmppChatWidget (IContainerWindow<Widget> window, XmppConversation conversation)
			:base (window, conversation)
		{
			Conversation.Closed += ConversationClosed;
			Conversation.MessageReceived += ConversationMessageReceived;
			Conversation.EventReceived += ConversationMessageReceived;
			Conversation.MessageSent += HandleMessageSent;
			Conversation.StateChanged += HandleStateChanged;
			
			Conversation.Session.Account.DisplayImageChange += AccountDisplayImageChanged;

			AddEventHandlers (conversation.PrimaryContact as XmppContact);
			
			HandleStateChanged (null, null);
		}
		
		public override void Initialize ()
		{ 
			base.Initialize ();
			
			AdjustForContacts ();
			
			ProcessLogs ();
			
			Update ();
			
			_messageEntry.PreprocessKeyPressed += HandlePreprocessKeyPressed;
			
			GtkActivityUtility.ClearTypeFromQueue (Conversation.PrimaryContact, ActivityTypes.Message);
			
			_nativeWidget.Show ();
		}
		
		public override void SwitchTo ()
		{
			Conversation.ChangeState (ChatState.Active);
			
			MenuUtility.FillToolBar ("/Galaxium/Gui/XMPP/ContainerWindow/Toolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
			MenuUtility.FillMenuBar ("/Galaxium/Gui/XMPP/ContainerWindow/Menu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
			MenuUtility.FillToolBar ("/Galaxium/Gui/XMPP/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
			
			Update ();
		}

		public override void SwitchFrom ()
		{
			CancelTyping ();
			Conversation.ChangeState (ChatState.Inactive);
			// TODO: add time-based inactivity
		}
		
		public override void Destroy ()
		{
			base.Destroy ();
			
			CancelTyping ();
			Conversation.ChangeState (ChatState.Gone);
			Conversation.Active = false;
			
			_messageEntry.PreprocessKeyPressed -= HandlePreprocessKeyPressed;
			
			Conversation.Closed -= ConversationClosed;
			Conversation.MessageReceived -= ConversationMessageReceived;
			Conversation.EventReceived -= ConversationMessageReceived;
			Conversation.MessageSent -= HandleMessageSent;
			Conversation.StateChanged -= HandleStateChanged;
			Conversation.Session.Account.DisplayImageChange -= AccountDisplayImageChanged;
			
			RemoveEventHandlers (Conversation.PrimaryContact as XmppContact);
		}
		
		public override IEntity GetEntity (string uid, string name)
		{
			if (uid == Session.Account.UniqueIdentifier)
				return Session.Account;

			return _conversation.PrimaryContact;
		}
		
		private void HandleStateChanged (object sender, EventArgs e)
		{
			var name = Conversation.Contact.DisplayIdentifier;
			switch (Conversation.State)
			{
				case ChatState.Unknown:
					_status_label.Text = String.Empty;
					_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_blank");
					break;
				case ChatState.Active:
					_status_label.Text = name + " is paying attention to the conversation";
					_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_blank");
					break;
				case ChatState.Composing:
					_activityImage.Animation = IconUtility.GetAnimation ("galaxium-typing_anim");
					_status_label.Text = name + " is now typing a message...";
					EmitBecomingActive ();
					break;
				case ChatState.Inactive:
					_status_label.Text = name + " is inactive";
					_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_blank");
					break;
				case ChatState.Gone:
					_status_label.Text = name + " is gone";
					_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_blank");
					break;
				case ChatState.Paused:
					_status_label.Text = name + " has paused typing";
					_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_stopped");
					break;
			}
		}
		
		protected override void GeneratePersonalLabel ()
		{
			//var name  = String.Empty;
			var label = new System.Text.StringBuilder ();
			var npmsg = "No personal message";
			
			var contact = (Conversation.PrimaryContact as XmppContact).InternalContact;

			//TODO: once gateway support is there, implement contact.Identifier
			//name = contact.Name ?? /* contact.vCard.Nickname ?? */ contact.Jid;
			
			label.Append ("<span size=\"small\"><i>");
			label.Append (contact.MainPresence.GetDescription () ?? npmsg);
			label.Append ("</i></span>");
			
			_personalLabel.Markup = label.ToString ();
			_personalLabel.Ellipsize = Pango.EllipsizeMode.End;
			_personalLabel.SingleLineMode = false;
			_personalLabel.Wrap = true;
		}

		private void HandlePreprocessKeyPressed (object sender, EventArgs e)
		{
			Conversation.ChangeState (ChatState.Composing);
			
			if (_typing_timer == null)
				_typing_timer = new Timer (TypingPaused, null, -1, -1);

			_typing_timer.Change (5000, -1);
		}

		private void CancelTyping ()
		{
			if (_typing_timer != null)
			{
				_typing_timer.Dispose ();
				_typing_timer = null;
			}
		}
		
		private void TypingPaused (object obj)
		{
			Conversation.ChangeState (ChatState.Paused);
		}
		
		protected override void MessageEntryTextSubmitted (object sender, SubmitTextEventArgs args)
		{
			base.MessageEntryTextSubmitted (sender, args);
			
			if (args.Text.Trim ().Length == 0) return;

			CancelTyping ();
			Conversation.SendMessage (args.Text);
			args.Submitted = true;
		}
		
		private void ConversationClosed (object sender, ConversationEventArgs args)
		{
			this.Close ();
		}
		
		private void ConversationMessageReceived (object sender, MessageEventArgs args)
		{
			_message_display.AddMessage (args.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			SoundSetUtility.Play (Sound.MessageReceived);
			
			EmitBecomeActive ();
		}

		private void HandleMessageSent (object sender, MessageEventArgs args)
		{
			SoundSetUtility.Play (Sound.MessageSent);
			_message_display.AddMessage (args.Message);
		}
		
		/*private void ContactSupressChanged (object sender, EntityChangeEventArgs<bool> args)
		{
			if ((Conversation.PrimaryContact as XmppContact).SupressImage)
				_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
			else {
				try { // FIXME: check for image's availability instead of exception handling
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (new Gdk.Pixbuf(Conversation.PrimaryContact.DisplayImage.ImageBuffer), 96, 96));
				}
				catch {
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
				}
			}
		}*/

		void ContactDisplayNameChanged (object sender, EntityChangeEventArgs<string> args)
		{
			_message_display.AddEvent (Message.Strip (args.Old, args.Entity, null)
			                           + " is now known as "
			                           + Message.Strip (args.New, args.Entity, null));
		}
		
		void ContactDisplayMessageChanged (object sender, EntityChangeEventArgs<string> args)
		{
			GeneratePersonalLabel ();
		}
		
		void ContactDisplayImageChanged (object sender, EntityChangeEventArgs<IDisplayImage> args)
		{
			var contact = args.Entity as XmppContact;
			
			if (contact == _conversation.PrimaryContact && contact.DisplayImage != null)
				SwitchContactImage (contact);
		}
		
		void ContactPresenceChanged (object sender, EntityChangeEventArgs<IPresence> args)
		{
			var contact = args.Entity as XmppContact;
			_message_display.AddEvent (contact.DisplayIdentifier + " is now " + args.New.State);
		}
		
		private void AccountDisplayImageChanged (object sender, EventArgs args)
		{
			_own_image.FadeTo (GenerateOwnImage ());
		}
		
		public override void SendFile (string filename)
		{
			throw new System.NotImplementedException ();
		}
		
		void AddEventHandlers (XmppContact contact)
		{
			contact.DisplayImageChange += ContactDisplayImageChanged;
			contact.DisplayMessageChange += ContactDisplayMessageChanged;
			contact.DisplayNameChange += ContactDisplayNameChanged;
			contact.PresenceChange += ContactPresenceChanged;
			
			//FIXME: Why is this disabled? We should listen for this option on any
			// protocol that has display images available and supressable.
			
			//contact.SupressChange += ContactSupressChanged;
		}
		
		void RemoveEventHandlers (XmppContact contact)
		{
			contact.DisplayImageChange -= ContactDisplayImageChanged;
			contact.DisplayMessageChange -= ContactDisplayMessageChanged;
			contact.DisplayNameChange -= ContactDisplayNameChanged;
			contact.PresenceChange -= ContactPresenceChanged;
			
			//FIXME: Why is this disabled? We should listen for this option on any
			// protocol that has display images available and supressable.
			
			//contact.SupressChange -= ContactSupressChanged;
		}

		void AdjustForContacts ()
		{
			if (_window.CurrentWidget == this)
				_window.GenerateTitle ();
			
			if (_conversation.ContactCollection.Count > 1)
			{
				ShowBox (ChatWidgetPositions.RightDisplayPane);
				HideBox (ChatWidgetPositions.RightInput);
				
				_window.Toolbar.ShowAll();
				_entryToolbar.ShowAll();
				
				ShowBox (ChatWidgetPositions.LeftInput);
				HideBox (ChatWidgetPositions.Identification);
			}
			else
			{
				HideBox (ChatWidgetPositions.RightDisplayPane);
				ShowBox (ChatWidgetPositions.RightInput);
				
				if (Conversation.PrimaryContact == null)
				{
					Anculus.Core.Log.Error ("There is no primary contact for the conversation this chat widget represents!");
					return;
				}

				var contact = Conversation.PrimaryContact as XmppContact;
				
				_window.Toolbar.Visible = contact.ShowActionToolbar;
				_entryToolbar.Visible = contact.ShowInputToolbar;
				
				SetBoxVisibility (ChatWidgetPositions.LeftInput, contact.ShowAccountImage);
				SetBoxVisibility (ChatWidgetPositions.RightInput, contact.ShowContactImage);
				SetBoxVisibility (ChatWidgetPositions.Identification, contact.ShowPersonalMessage);

				GeneratePersonalLabel ();
				
				SwitchContactImage (contact);
			}
		}
		
		public override void LoadFont ()
		{
			MenuUtility.FillToolBar ("/Galaxium/Gui/XMPP/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
		}
		
		public override void SaveFont ()
		{
			
		}
		
		protected override void OwnImageButtonPressEvent (object sender, ButtonPressEventArgs args)
		{
			throw new NotImplementedException ();
		}
	}
}
